﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using btl.generic;
using SdlDotNet.Core;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Sprites;
using SdlDotNet.Input;

namespace Eater
{
    class Program : IDisposable
    {
        Color screenColor = Color.White;
        const int EaterCount = 16;
        const int FoodCount = 100;
        const int Width = 1000;
        const int maxIteration = 600;
        const bool drawFoods = true;

        List<EaterModel> eaters = new List<EaterModel>();
        Dictionary<EaterStatus, Sprite> sprites = new Dictionary<EaterStatus, Sprite>();
        List<Food> foods = new List<Food>();
        Sprite foodSprite;
        SdlDotNet.Graphics.Font font;
        Surface textSurface;

        Random random = new Random();
        Surface screen;
        int iterationCount;
        int generationCount;
        double bestFitness = 0;

        GA geneticAlgorithm;

        ResultFile results = new ResultFile("genedata.txt");

        // For graphics centering
        Vector2 foodDelta = new Vector2(2, 2);
        Vector2 eaterDelta = new Vector2(10, 10);

        [STAThread]
        static void Main(string[] args)
        {
            Program app = new Program();
            app.Go();
        }

        public Program()
        {
            screen = Video.SetVideoMode(Width, 500);
            Video.WindowCaption = "Hello World!";
        }

        public void Go()
        {
            Events.KeyboardDown += Events_KeyboardDown;
            Events.Quit += new EventHandler<QuitEventArgs>(this.Quit);
            Events.Tick += Events_Tick;

            sprites.Add(EaterStatus.Hungry, new Sprite(new Surface("eater_yellow.png")));
            sprites.Add(EaterStatus.Full, new Sprite(new Surface("eater_green.png")));
            sprites.Add(EaterStatus.Dying, new Sprite(new Surface("eater_red.png")));
            foodSprite = new Sprite(new Surface("food.png"));

            font = new SdlDotNet.Graphics.Font("FreeSans.ttf", 12);
            textSurface = font.Render(string.Format("Gen: {0}, Iter: {1}, Best Fit: {2}", generationCount, iterationCount, bestFitness), Color.Red);

            Init();

            Events.Run();
        }

        double FitnessFunction(Genome genome)
        {
            EaterModel eater = eaters.First(x => x.Genome == genome);
            return eater.Fitness;
        }

        void MakeFood(int n = 1)
        {
            for (int i = 0; i < n; i++)
            {
                foods.Add(new Food()
                {
                    Position = new Vector2(random.Next(Width), random.Next(500))
                });
            }
        }

        void Init()
        {
            foods.Clear();
            MakeFood(FoodCount);

            eaters.Clear();
            for (int i = 0; i < EaterCount; i++)
            {
                var eater = new EaterModel(foods)
                {
                    Position = new Vector2(random.Next(Width), random.Next(500))
                };
                //eater.SetClosestFood(foods);
                eaters.Add(eater);
            }

            if (geneticAlgorithm == null)
            {
                geneticAlgorithm = new GA(0.50, 0.01, EaterCount, 3000);
                geneticAlgorithm.Elitism = true;
                geneticAlgorithm.FitnessFunction = FitnessFunction;
                int numWeightsInNN = eaters[0].NumberOfWeights;
                geneticAlgorithm.InitGenomes(numWeightsInNN);
            }

            //Get the weights from the GA and insert into the sweepers brains
            var population = geneticAlgorithm.ThisGeneration;
            for (int i = 0; i < eaters.Count; i++)
            {
                eaters[i].Genome = population[i];
            }

            iterationCount = 0;
            generationCount++;
        }

        // Subtick 1/60 seconds
        void Update()
        {
            iterationCount++;

            // Draw foods
            if (drawFoods)
            {
                foreach (Food food in foods)
                {
                    screen.Blit(foodSprite, (food.Position - foodDelta).ToPoint());
                }
            }

            // Update eater status
            for (int i = 0; i < eaters.Count; i++)
            {
                var eater = eaters[i];
                eater.Update();

                // Are we eating?
                if (eater.DistanceTo(eater.TargetFood) < 12)
                {
                    // Yes
                    Food removeFood = eater.TargetFood;
                    foods.Remove(removeFood);
                    eater.LastFood = 0;
                    eater.Status = EaterStatus.Full;
                    eater.Fitness++;
                    MakeFood();
                }

                var status = eater.Status;
                if (status == EaterStatus.Dead)
                {
                    eaters.RemoveAt(i);
                    i--;
                    continue;
                }

                // Draw eaters
                if (drawFoods)
                {
                    screen.Blit(sprites[eater.Status], (eater.Position - eaterDelta).ToPoint());
                }
            }

            if (iterationCount == maxIteration)
            {
                iterationCount++;
                var highScores = eaters.OrderByDescending(x => x.Fitness);
                
                results.WriteResult(highScores.First().Genome.Genes.Concat(new double[]{highScores.First().Fitness / 100.0}).ToArray());
                /*
                Console.WriteLine("Winner genome: ");
                foreach (double g in highScores.First().Genome.Genes())
                {
                    Console.WriteLine(g);
                }*/
                bestFitness = highScores.First().Fitness;

                geneticAlgorithm.RankPopulation();
                geneticAlgorithm.CreateNextGeneration();

                Init();
                
                iterationCount = 0;
            }
        }

        void Events_KeyboardDown(object sender, SdlDotNet.Input.KeyboardEventArgs e)
        {
            if (e.Key == Key.Escape || e.Key == Key.Q)
            {
                Events.QuitApplication();
            }
            else if (e.Key == Key.Return)
            {
                Init();
            }
            else if (e.Key == Key.F)
            {
                fast = !fast;
            }
        }

        float time = 0;
        bool fast = false;
        double secondCounter = 0;

        void Events_Tick(object sender, TickEventArgs e)
        {
            screen.Fill(screenColor);

            secondCounter += e.SecondsElapsed;
            if (secondCounter > 1.0)
            {
                if (textSurface != null)
                {
                    textSurface.Dispose();
                }
                textSurface = font.Render(string.Format("Gen: {0}, Iter: {1}, Best Fit: {2}", generationCount, iterationCount, bestFitness), Color.Red);
                secondCounter = 0;
            }

            if (fast)
            {
                time += e.SecondsElapsed;
                int numSubsteps = (int)(e.SecondsElapsed / 0.001f);
                numSubsteps = Math.Min(numSubsteps, 100);
                while (numSubsteps > 0)
                {
                    Update();
                    numSubsteps--;
                }
            }
            else
            {
                Update();
            }
            Video.Screen.Blit(textSurface);

            screen.Update();
        }

        private void Quit(object sender, QuitEventArgs e)
        {
            Events.QuitApplication();
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                foreach (Sprite sprite in sprites.Values)
                {
                    sprite.Dispose();
                }
                sprites.Clear();

                foodSprite.Dispose();
                foodSprite = null;

                font.Dispose();
                font = null;

                if (textSurface != null)
                {
                    textSurface.Dispose();
                }
            }
        }

        void IDisposable.Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}
